/*
* RTSP Server
* CPSC 317 2012W2
* Veronika Dikoun
* Billy Lin
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include "rtspd.h"

#include <stdint.h>
#include <string.h>     // for string stuff
#include <pthread.h>    // for threads

#include "fsm.h"
#include "cloud_helper.h"

#define BACKLOG 10     // how many pending connections queue will hold

/*
 * Get the IP address (v4 or v6) of a sockaddr structure,
 * sa could point to an IPv4 or IPv6 address buffer.
 */
void *get_in_addr(struct sockaddr *sa)
{
	// AF_INET specifies an IPv4 address family.
	if (sa->sa_family == AF_INET)
	{
		struct sockaddr_in *sockaddr_4 = (struct sockaddr_in*) sa;
		return &(sockaddr_4->sin_addr);
	}
	// AF_INET specifies an IPv6 address family.
	else if (sa->sa_family == AF_INET6)
	{
		struct sockaddr_in6 *sockaddr_6 = (struct sockaddr_in6*) sa;
		return &(sockaddr_6->sin6_addr);
	}
	//There are many other address families, e.g.: IrDA/Bluetooth which we will not deal with
	return 0;
}

/**
 * Hints provide information on what socket type we want to use!
 */
void init_hints(struct addrinfo *hints)
{
	memset(hints, 0, sizeof *hints);
	hints->ai_family = AF_UNSPEC;
	hints->ai_socktype = SOCK_STREAM;	// reliable sequenced byte streams
	hints->ai_flags = AI_PASSIVE;		// use whatever my current IP is!
}

int get_addrinfos(char* service_port, struct addrinfo **server_addrinfo)
{
	struct addrinfo hints;
	init_hints(&hints);
	int return_value;

	return_value = getaddrinfo(NULL, service_port, &hints, server_addrinfo);

	if (return_value != 0)
	{
		//Oh noes, error!
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(return_value));
	}

	return return_value;
}

int create_socket_and_bind(struct addrinfo* servinfo)
{
	int listen_socket_fd;
	struct addrinfo *p;
	int yes = 1;
	int temp_rv; //return value

	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
		listen_socket_fd = socket(p->ai_family, p->ai_socktype,
						p->ai_protocol);

		if (listen_socket_fd == -1)
		{
			perror("rtspd: socket");
			continue;
		}

		temp_rv = setsockopt(listen_socket_fd, SOL_SOCKET, SO_REUSEADDR, &yes,
				sizeof(int));
		// Allow me to reuse old ports
		if (temp_rv == -1)
		{
			perror("setsockopt");
			exit(1);
		}

		// Bind the port to the socket
		temp_rv = bind(listen_socket_fd, p->ai_addr, p->ai_addrlen);
		// 0 if successful
		// -1 if unsuccessful
		if (temp_rv == -1)
		{
			close(listen_socket_fd);
			perror("rtspd: bind");
			continue;
		}

		break;
	}

	if (p == NULL)
	{
		perror("rtspd: failed to bind\n");
		return 2;
	}

	return listen_socket_fd;
}

/*
* service_port: Service name, like "http", or the port number as a string.
*/
int serve(char* service_port)
{
	// fd = file descriptor
	int listen_socket_fd;	// listen on this socket fd
	struct addrinfo *server_addrinfo;		// contains address info of this server

	// Fill in structure
	if (get_addrinfos(service_port, &server_addrinfo))
	{
		return 1; //an error occurred!
	}
	// servinfo now points to a linked list of 1 or more struct addrinfos

	listen_socket_fd = create_socket_and_bind(server_addrinfo);

	freeaddrinfo(server_addrinfo); // all done with this structure

	// Enable socket to accept connections!
	// 0 if successful
	// -1 if unsuccessful
	if (listen(listen_socket_fd, BACKLOG) == -1)
	{
		perror("listen");
		exit(1);
	}

	printf("rtspd: Waiting for connections on %s...\n", service_port);

	accept_clients_loop(listen_socket_fd);

	return 0;
}

/**
 * client_addr Client socket address information
 */
void accept_clients_loop(int listen_socket_fd)
{
	// sockaddr_storage is a generic structure to store socket
	// address, port, etc. and can hold both IPv4 and v6 addresses

	// generic structure to store client socket address info
	struct sockaddr_storage		client_sockaddr_storage;

	// for cast of generic structure to actual structure
	struct sockaddr				*client_sockaddr;

	int connect_socket_fd; //accept new connections on this!

	socklen_t sin_size;

	while (1)
	{
		// main accept() loop
		sin_size = sizeof client_sockaddr_storage;

		client_sockaddr = (struct sockaddr *) &client_sockaddr_storage;

		// accept a connection request
		connect_socket_fd = accept(listen_socket_fd, client_sockaddr, &sin_size);
		// 0 if successful
		// -1 if unsuccessful
		if (connect_socket_fd == -1)
		{
			perror("accept");
			continue;
		}

		create_client_thread(connect_socket_fd);

		print_got_ip(&client_sockaddr_storage);

	}
}

void create_client_thread(int socket_fd)
{
	pthread_t thread;
	// Creates the thread and calls the function serve_client.
	pthread_create(&thread, NULL, serve_client, (void *) (intptr_t) socket_fd);
	// Detaches the thread. This means that, once the thread finishes, it is destroyed.
	pthread_detach(thread);
}

// Convert an IP address buffer into a string, then print!
void print_got_ip(struct sockaddr_storage* addr)
{
	char ip_string[INET6_ADDRSTRLEN];

	// Cast to generic socket address structure
	struct sockaddr *socket_addr = (struct sockaddr *) addr;

	// Extract the IP address, which could be either
	// an IPv4 struct or IPv6 struct.
	void* ip_address = get_in_addr(socket_addr);

	// inet_ntop = number -> print
	// convert the IP address to a string
	inet_ntop
	(
		addr->ss_family,
		ip_address,
		ip_string,
		sizeof ip_string
	);

	printf("rtspd: Got a connection from %s !\n", ip_string);
}

void *serve_client(void *ptr) {

	int client_fd = (int) (intptr_t) ptr;
	int buf_size = 512; // TODO: replace with named constant
	char* in_buf = (char*) malloc (sizeof(char)*buf_size);
	char* out_buf = (char*) malloc (sizeof(char)*buf_size);
	int byte_count;

	stream_state *current_state = (stream_state*)malloc(sizeof(stream_state));
	init_stream_state(current_state, client_fd);

	while(1)
	{
	    memset( out_buf, '\0', buf_size );
		memset( in_buf, '\0', buf_size );

		usleep(10000);
		byte_count = recv(client_fd, in_buf, buf_size, 0);
		// -1 on failure
		// 0 if client has disconnected
		// otherwise successful
		if (byte_count == -1) {
            perror("recv\n");
            return 0;
		}
		if (byte_count == 0) {
            // handle client shutdown
            printf("client %d disconnected\n", client_fd);
            return 0;
		}

		printf("received %d bytes of data from client %d\n", byte_count, client_fd);

		// Defer handling to the FSM
		handle_client_request(current_state, byte_count, in_buf, out_buf);

        // -1 on failure
        // 0 on success
		if (send(client_fd, out_buf, strlen(out_buf), 0) == -1)
		{
			perror("send");
		}
	}

    // -1 on failure
    // 0 on success
	if (close(client_fd) == -1) {
        perror("close\n");
        return 0;
	}

	return 0;
}
